/*
placeholder class...intended to be used as a surrogate for various Enyaras
*/

class LevObject extends KActor
   placeable;

var Actor Seeking;
var vector InitialDir;
var vector Dir;
var float Speed;
var float DefaultSpeed;

//copied from goodkarma
var bool bCollisionDamage;
var float DamageSpeed, HitDamageScale;
var class<DamageType> HitDamageType;
var() array<Sound> HitSounds;
var() byte RelativeImpactVolume ;           //The relative sound volume of the hit sound


//hovering
var float TickCounter;

var vector msILoc, msFLoc; //initial & final loc for movesmooth
var float msDistance; //total distance
var vector msDir; //unit vector pointing from object to player
const smoothSpeed = 0.25; //time it should take for object to get to player
var vector vX, vY, vZ;
var rotator holdRot;
const playerProximityLimit = 400; //constant value...distance object can be from player before it stops
const holdHeight = 100; //how high off teh ground should the player hold the object?


var float ImpactTime; //how long to wait before adding another Impulse
var float HoverForceMag;
var vector HoverUnitVector;
var float AttachHeight;
var bool tooLow, tooHigh;
var bool firstHoverTick;
var float HoverImpulse;
var float LastTick;
var float LastForceMeasurement;
var float b, k; //damping constant
var bool bPeak, bZero; //used for tracking oscilations
var float zMax; //same as above

var vector SpringAttachLocation;
var float defaultAttachOffset; //the initial height above the object where the spring is attached

var bool bIsLevitated; //wheter or not the obj is currently being levved
                       //   if so, there must be a PC doing the levving
                       //   also, there must be a current springattachlocation...
var vector LevForce; //used in KApplyForce to determine which way to lev

var float AffectedCheckTime, AffectedCheckFrequency;
var float LastDamagedTimeStamp;
var vector TargetPosition; //where this object wants to move to; aka the source of the telek dmg (aka the player)

//doubletap push/pull values
var bool bIsPushed, bIsPulled;

var bool bIsPulledWhileLev;

var bool bIsHeld;  //if player is 'holding' object

var Actor originalBase; //when the object is picked up, setbase(player) is called...setbase(originalbase)
                        //will be called when it is put down

var LevManager LManager;
var EnyaraPawn MainPlayerPawn; //reference to player pawn...so object knows what not to hit...

//values changed during lev - need to be reset afterwards
struct LevValues{
    var float KMass;
};
var LevValues iValues, lValues;

static final function float Logarithm (float a, optional float Base){
  if (Base==0)
    Base=10;
  return Loge(a)/Loge(base);
}




function Tick(float dt){
    local vector ForceDir;
    local float AffectedDuration; //length of time between last telek dmg hit
    local float ForceMag;
    local float VelMag;

    if (MainPlayerPawn != none){
        //log("BLAH BLAH BLAH");
        if (vsize(self.Location - MainPlayerPawn.Location) <= playerProximityLimit && self.bIsPulled){
            //xxEnyaraPlayerController(MainPlayerPawn.Owner).ClientMessage("LEVOBJECT CLOSE TO PAWN LOCATION ("$vsize(self.Location - MainPlayerPawn.Location)$")");
            if (self.Physics == PHYS_Karma){
                self.SetPhysics(PHYS_None);
                self.setPhysics(PHYS_Karma);
                self.KWake();
                self.bIsPulled = false;

                self.GotoState('SmoothMoving');
            }

        }
       /* else if (vsize(self.Location - MainPlayerPawn.Location) <= playerProximityLimit && self.bIsPulledWhileLev){
            EnyaraPlayerController(MainPlayerPawn.Owner).ClientMessage("LEVOBJECT CLOSE TO PAWN LOCATION ("$vsize(self.Location - MainPlayerPawn.Location)$")");
            if (self.Physics == PHYS_Karma){
                //self.SetPhysics(PHYS_None);
                //self.setPhysics(PHYS_Karma);
                //self.KWake();
                self.SetPhysics(PHYS_None);
                self.bCollideWorld = true;
                self.bIsHeld = true; //trigger holding functionality in LevManager tick()

                self.bIsPulledWhileLev = false;
            }

        }
         */
    }

    //log("LEVOBJECT CLOSE TO PAWN LOCATION ("$vsize(self.Location - LManager.P.Location)@"units away)");



    AffectedDuration = Level.TimeSeconds - LastDamagedTimeStamp;

    super.Tick(dt);

}

//############## START ACCESSOR FXNS ####################
function float debugsetb(float n){
    self.b = n;
    return b;
}
function debugsetm(float n){
    self.KSetMass(n);
}
function debugsetg(float n){
    self.KSetActorGravScale(n);
}
function debugsetk(float n){
    self.k = n;
}

function setSpringAttachLocation(vector A){
    self.SpringAttachLocation = A;
}
function vector getSpringAttachLocation(){
    return self.SpringAttachLocation;
}

function SetIsLevitated(bool b){
    bIsLevitated = b;
    log(self.name$".bIsLevitated set to"@b);
}
function bool IsLevitated(){
    //return bIsLevitated;
    if (self.IsInState('Hovering') || self.IsInState('PlayerHolding')){
        return true;
    }
    else{
        return bIsLevitated;
    }
}

//the 'teardown' function fo this object - called when a player is done leving this object
function LevDesynch(){
    self.LManager = none;
    self.SetIsLevitated(false);
    self.bIsPulled = false;
    self.bIsPushed = false;

    /*if (self.bIsHeld){
        self.bIsHeld = false;
        self.SetPhysics(PHYS_Karma);
        self.KWake();
    } */

    self.SpringAttachLocation = vect(0,0,0);  //reset invisible spring
    self.TickCounter = 0;
    log("# LManager has been cleared");
    log("#####");
    self.KSetMass(iValues.KMass);

    //if still PHYS_None from smoothmove, reset to karma and wake up
    if (self.Physics != PHYS_Karma){
        self.SetPhysics(PHYS_Karma);
        self.KWake();
    }

    self.GotoState('LevObject');

}
//################ END ACCESSOR FXNS ####################

//intercept the official takedamageq fxn call and instead initiate lev
event TakeDamage ( int Damage, Pawn EventInstigator, vector HitLocation,
                   vector Momentum, class<DamageType> DamageType ){

      if(damageType == class'Enyara.DamTypeLev'){
          log("LEVOBJECT damaged by DamTypeLev. Now allowing lev operations");
          //xxEnyaraPlayerController(Level.GetLocalPlayerController()).ClientMessage("LEV DAMAGE!!");
          SetIsLevitated(true);
          tickCounter = Level.TimeSeconds; //initial lev timestamp
      }
      else if (damageType == class'Enyara.DamTypePush'){
          //xxEnyaraPlayerController(Level.GetLocalPlayerController()).ClientMessage("PUSH DAMAGE!!");
          self.bIsPushed = true;
      }
      else if (damageType == class'Enyara.DamTypePull'){
          //xxEnyaraPlayerController(Level.GetLocalPlayerController()).ClientMessage("PULL DAMAGE!!");
          self.bIsPulled = true;
      }
      else {
          log(self.name@"hit by normal damage type...passing stuff up to super");
          super.TakeDamage(Damage, EventInstigator, HitLocation, Momentum, damageType);
      }
}

//called to apply impulse
function AddImp(optional vector Impulse, optional vector Location){
    //:TODO: calculate hoverimpulse based on object's KParams
    HoverImpulse = 30000; //on initial takedamage
    if (Location == vect(0,0,0)) Location = vect(0,0,0); //default to CoM
    if (Impulse == vect(0,0,0)) Impulse = HoverImpulse * vect(0,0,1);
    self.KAddImpulse(Impulse, Location);
}

function vector DetermineSpringForce(float dt){
    local vector F, x, v, tmp, myConstant; //F = -kx - bv (k = spring stiffness, b = damping);
    local vector COMLocation, restPosition;
    local float Zrestoffset;


    //self.KGetDampingProps(b, tmp); // unitless?
    if (b == 0.0) b = 100; //used to be .80
    if (k == 0.0) k = 1000; // N/m (used to be 625...)
    if (self.KGetMass() == 0.5) self.KSetMass(0.3);

    ZrestOffset = (self.Mass * 9.8 * 52.5) / K;
    //restHeight =

     //:TODO: base this off peaks of waves
    /*if (Level.TimeSeconds - LastForceMeasurement > 0.5){
        tmp = Logarithm(abs(v), 2);
        if (v > 0) v -= tmp;
        else if (v < 0) v += tmp;

        log("DECREASED V BY"@tmp);
    }*/

    if (SpringAttachLocation == vect(0,0,0)){
        //:TODO: find out if self.Location is the center of mass or what...where on the static mesh does it measure to?
        SpringAttachLocation = self.Location + (vect(0, 0, 1) *defaultAttachOffset);
        LManager.printscreen("defaultAttachOffset:"@defaultAttachOffset);
    }

    //get the location of the objects Center of Mass
    self.KGetCOMPosition(COMLocation);

    x = (COMLocation - SpringAttachLocation) / 52.5; //from CoM to AttachHeight (in UU => m)
    v = self.Velocity / 52.5; //assuming in UU/s -> M
    //v = normal(self.Velocity) * 10;
    F = (-k * x) + (-b*v);  //vectors: F, x, and v   scalars: k and b

    //track ticks
    LastForceMeasurement = Level.TimeSeconds;

    return F * 52.2; //convert Kg*m/s^2 => KG*UU/s^2
}

function vector DetermineLinearImpulse(float dt){
    local float e; //coefficient of restitution (elasticity...0=lump of clay, 1=superball)
    local vector n; //normal vector along zaxis
    local float m; //mass (:TODO: do something with the KMass value too...)
    local vector F; //force

    F = DetermineSpringForce(dt);
    //Fangular = //:TODO: angular force (torque)
    //log("Force:"@F@"Impulse:"@(F * dt));
    return (F * dt);
}
/*
function vector DetermineAngularImpulse(float dt){
    //:TODO:
}
*/

state PlayerHolding {
    function Tick(float dt){
        Lmanager.LevDevice.GetViewAxes(vX,vY,vZ);
        self.MoveSmooth((Lmanager.P.Location + (playerProximityLimit*vX) + (vect(0,0,1)*holdHeight) ) - self.Location);
        holdRot = self.Rotation;
        holdRot.Roll += 10 % 65536;
        holdRot.Pitch += 10 % 65536;
        holdRot.Yaw += 10 % 65536;
        self.SetRotation(holdRot);
    }

Begin:
}

state SmoothMoving {
    function Tick(float dt){
        if ( (vsize(self.Location - msFloc) >= playerProximityLimit) && msILoc != vect(0,0,0) && msFLoc != vect(0,0,0) && msDistance != 0.0 && msDir != vect(0,0,0) ){
            //assume we want the levobject to travel all the way to the player in 1 second,
            //and that the object is d units away. each tick (dt), move the object d*dt units towards
            //the player...
            self.MoveSmooth(msDir * (msDistance * (dt/smoothSpeed) ) );
        }

        if  (vsize(self.Location - msFloc) <= playerProximityLimit){
            LManager.printscreen("DONE SMOOTHING");
            self.GotoState('PlayerHolding');
        }
    }

Begin:
    self.SetPhysics(PHYS_None);
    self.bCollideWorld = true;
    log("LevObject is in state SmoothMoving");
    msILoc = self.Location;
    msFLoc = LManager.P.Location + vect(0,0,1)*holdHeight; //the pawn location + the holdHeight offset (on z axis)
    msDistance = vsize(msFloc - msILoc);
    msDir = Normal(msFloc - msILoc);
}

state Hovering {

    function Tick(float dt){
         if (self.IsLevitated()){
            self.AddImp(DetermineLinearImpulse(dt));
        //    if (TickCounter == 0) TickCounter = Level.TimeSeconds;
        }
        //}
        //log(Level.TimeSeconds$": v ="@self.Velocity.Z@"h ="@self.Location.Z@"Imp ="@DetermineLinearImpulse(dt));

        super.Tick(dt);
    }


    /*event KApplyForce ( out vector Force, out vector Torque ){
         //if (bIsLevitated && self.Location.Z < AttachHeight && abs(self.Velocity.Z) < 1000){
            //log("Current Height:"@self.Location.Z);
           // Force = HoverForceMag*HoverUnitVector;

                     //super.KApplyForce( Force, Torque );
         //}
         super.KApplyForce(Force, Torque);
         log(Level.TimeSeconds$": F ="@Force.Z@"kmass ="@self.KGetMass()@"mass ="@self.Mass@"kgravscale ="@self.KGetActorGravScale());
      }*/

Begin:
    log("LevObject is at state"@self.GetStateName());
    //store values
    iValues.KMass = self.KGetMass();
    //set values
    self.KSetMass(0.3);
    ImpactTime = 0.50;
    HoverForceMag = 150;
    HoverUnitVector = vect(0,0,1);
    DefaultAttachOffset = 150;
    tooLow = true;
    tooHigh = false;
  //  While(true){
        if (LManager == none || !LManager.AcceptingInput()){
            log("LevObject returning to state 'LevObject'");
            GoToState('LevObject');
        }
    //}
}


event RanInto(Actor Other)
{
    local vector Momentum;
    local float Speed;
    local int A ;

    if(bCollisionDamage && Other != None)
    {
        if (Pawn(Other) == None || Vehicle(Other) != None || Other == Instigator || Other.Role != ROLE_Authority)
            return;

        Speed = VSize(Velocity) ;

        //log(Velocity) ;

        if (Speed > DamageSpeed)
        {
            Momentum += Speed * 0.25 * Other.Mass * Normal(Velocity cross vect(0,0,1));
            Other.TakeDamage(int(Speed * 0.045 * HitDamageScale), Instigator, Other.Location, Momentum, HitDamageType);
            if(HitSounds.length != 0)
         {
                A = Rand(HitSounds.length) ;
                PlaySound(HitSounds[a] , SLOT_Ambient, (RelativeImpactVolume * ((Speed / DamageSpeed) / Mass)) * 1.35 );
         }
            //DEBUG
            log(Tag$" injured "$Other.Tag$" dealing "$(int(Speed * 0.045 * HitDamageScale))$" Damage!") ;
            if((Speed * 0.045 * HitDamageScale) >= 70)
                TakeDamage(5, instigator, Location, ((Velocity * -0.85) / (mass + 1)), class'Enyara.DamTypeBludgeon') ;
            else
                TakeDamage(5, instigator, Location, ((Velocity * -0.75) / (mass + 1)), class'Enyara.DamTypeBludgeon') ;
        }
        else if(Pawn(Other) != None)
                TakeDamage(5, instigator, Location, (Velocity * -1.03), class'Enyara.DamTypeBludgeon') ;
        //ShoveCountDown = 3 ;

        //log(Velocity) ;
    }

    //DEBUG
    //log(Tag$" is now moving at "$Velocity) ;
}



defaultproperties{
    //Speed=1350.000000

   // Begin Object class=KarmaParamsRBFull Name=KParams30
         //KLinearDamping=2.000;
         //KAngularDamping=2.000;
     //    KStartEnabled=True
        // bHighDetailOnly=False
        // bClientOnly=False
        // bKDoubleTickRate=False
        // KFriction=1.600000
        // KActorGravScale = 0.0;
    // KMass=2;
       //  name="KParams30"
     //End Object
     //KParams=KarmaParamsRBFull'Enyara.LevObject.KParams30'

     bPeak = false
     bZero = false
     bCollideActors=True
     bCollideWorld=True
     bIsLevitated=false
     AffectedCheckFrequency=0.2
     DefaultSpeed=100 //in meters per second (1m = 52.5UU)
     DrawType=DT_StaticMesh
     StaticMesh=StaticMesh'MiscPhysicsMeshes.Barrels.Barrel'
     //StaticMesh=StaticMesh'HourMoriaStatics.crateX'
     //StaticMesh=StaticMesh'X_MiscPhysicsMeshes.Crates.Crate'
     bNoDelete = false
     DefaultAttachOffset = 150
     FirstHoverTick = true

    // bBlockedPath=True
    // moveDistance=50.000000
     bCollisionDamage=True
     DamageSpeed=600.000000
     HitDamageType=class'Enyara.DamTypeBludgeon'
     HitDamageScale=1.000000
     RelativeImpactVolume=127
     //bShoveable=True
     //ShoveModifier=1.000000
     //bRaytraceLighting=True
     bDramaticLighting=True
     RemoteRole=ROLE_SimulatedProxy
     bNetNotify=True
     Begin Object Class=KarmaParams Name=KarmaParams0
         KMass=0.500000
         bHighDetailOnly=False
         bClientOnly=False
         KFriction=0.500000
         KRestitution=0.200000
         KImpactThreshold=100000.000000
     End Object
     KParams=KarmaParams'GoodKarma.NetKActor.KarmaParams0'
     bPathColliding=True
}




